﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Kokomo.Mirror.PeFormat;
using System.Globalization;

namespace Kokomo.Mirror
{
	public static class StringScanner
	{

		public static bool IsLatinCharacter(char c)
		{
			UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c);

			bool isEnglish = false;
			switch (unicodeCategory)
			{
				case UnicodeCategory.ClosePunctuation:
				case UnicodeCategory.ConnectorPunctuation:
				case UnicodeCategory.CurrencySymbol:
				case UnicodeCategory.DashPunctuation:
				case UnicodeCategory.DecimalDigitNumber:
				case UnicodeCategory.FinalQuotePunctuation:
				case UnicodeCategory.InitialQuotePunctuation:
				case UnicodeCategory.LineSeparator:
				case UnicodeCategory.LowercaseLetter:
				case UnicodeCategory.MathSymbol:
				case UnicodeCategory.NonSpacingMark:
				case UnicodeCategory.ModifierLetter:
				case UnicodeCategory.ModifierSymbol:
				case UnicodeCategory.OpenPunctuation:
				case UnicodeCategory.SpaceSeparator:
				case UnicodeCategory.SpacingCombiningMark:
				case UnicodeCategory.TitlecaseLetter:
				case UnicodeCategory.UppercaseLetter:
				case UnicodeCategory.OtherPunctuation:
					isEnglish = true;
					break;
				case UnicodeCategory.OtherNumber:
				case UnicodeCategory.LetterNumber:
				case UnicodeCategory.Control:
				case UnicodeCategory.EnclosingMark:
				case UnicodeCategory.Format:
				case UnicodeCategory.OtherLetter:
				case UnicodeCategory.OtherNotAssigned:
				case UnicodeCategory.ParagraphSeparator:
				case UnicodeCategory.PrivateUse:
				case UnicodeCategory.Surrogate:
				case UnicodeCategory.OtherSymbol:
				default:
					isEnglish = false;
					break;
			}

			return isEnglish;
		}

		public static ScannedString? TryReadString(
			Stream stream,
			long bytesToScan = -1
			)
		{
			string invalidString = "invalid";
			DecoderFallback fallback = new DecoderReplacementFallback(invalidString);
			Encoding encoding = Encoding.GetEncoding(Encoding.Unicode.EncodingName, new EncoderExceptionFallback(), fallback);

			const int minimumThreshold = 3;

			long streamStartOffset = stream.Position;
			long streamEndOffset = (bytesToScan < 0) ? stream.Length : streamStartOffset + bytesToScan;

			StringBuilder builder = new StringBuilder();
			byte[] buffer = new byte[2];
			while (stream.Position < streamEndOffset)
			{
				int bytesRead = stream.Read(buffer, 0, 2);
				if (bytesRead < 2) return null;

				int v = buffer[0] | buffer[1] << 8;
				bool isTerminal = true;
				if (v != 0)
				{
					string decoded = new string(encoding.GetChars(buffer, 0, 2));
					if (
						(decoded != invalidString)
						&& (!string.IsNullOrEmpty(decoded) && IsLatinCharacter(decoded[0]))
						)
					{
						isTerminal = false;

						builder.Append(decoded);
					}
				}

				if (isTerminal)
				{
					if (
						(builder.Length > minimumThreshold)
						)
					{
						ScannedString scanned = new ScannedString(
							streamStartOffset,
							builder.ToString()
							);

						return scanned;
					}
				}
			}

			return null;
		}
		//public static IEnumerable<ScannedString> Scan(
		//    Stream stream,
		//    Action<int> progressDelegate = null,
		//    long bytesToScan = -1
		//    )
		//{
		//    string invalidString = "invalid";
		//    DecoderFallback fallback = new DecoderReplacementFallback(invalidString);
		//    Encoding encoding = Encoding.GetEncoding(Encoding.Unicode.EncodingName, new EncoderExceptionFallback(), fallback);

		//    const int minimumThreshold = 3;

		//    if (bytesToScan < 0)
		//        bytesToScan = (int)(stream.Length - stream.Position);

		//    int? startOffset = null;
		//    StringBuilder builder = new StringBuilder();
		//    byte[] buffer = new byte[2];
		//    for (int i = 0; i < bytesToScan; i += 2)
		//    {
		//        int bytesRead = stream.Read(buffer, 0, 2);
		//        if (bytesRead < 2) yield break;

		//        int v = buffer[0] | buffer[1] << 8;
		//        bool isTerminal = true;
		//        if (v != 0)
		//        {
		//            string decoded = new string(encoding.GetChars(buffer, 0, 2));
		//            if (
		//                (decoded != invalidString)
		//                && (!string.IsNullOrEmpty(decoded) && IsLatinCharacter(decoded[0]))
		//                )
		//            {
		//                isTerminal = false;
		//                if (!startOffset.HasValue)
		//                    startOffset = i;

		//                builder.Append(decoded);
		//            }
		//        }

		//        if (isTerminal)
		//        {
		//            if (
		//                startOffset.HasValue
		//                && (builder.Length > minimumThreshold)
		//                )
		//            {
		//                ScannedString scanned = new ScannedString(
		//                    startOffset.Value,
		//                    builder.ToString()
		//                    );

		//                yield return scanned;
		//            }

		//            startOffset = null;
		//            builder.Clear();
		//        }

		//        if (progressDelegate != null) progressDelegate(i);
		//    }
		//}
		public static IEnumerable<ScannedString> Scan(
			Stream stream,
			Action<long> progressDelegate = null,
			long bytesToScan = -1
			)
		{
			string invalidString = "invalid";
			DecoderFallback fallback = new DecoderReplacementFallback(invalidString);
			Encoding encoding = Encoding.GetEncoding(Encoding.Unicode.EncodingName, new EncoderExceptionFallback(), fallback);

			const int minimumThreshold = 3;

			long streamStartOffset = stream.Position;
			long streamEndOffset = (bytesToScan < 0) ? stream.Length : streamStartOffset + bytesToScan;

			int? startOffset = null;
			StringBuilder builder = new StringBuilder();
			byte[] buffer = new byte[2];
			while (stream.Position < streamEndOffset)
			{
				ScannedString? scannedString = TryReadString(stream, streamEndOffset - stream.Position);
				if (scannedString.HasValue)
					yield return scannedString.Value;

				if (progressDelegate != null) progressDelegate(stream.Position - streamStartOffset);
			}
		}
	}
}
